package com.www.pagingdemo.data

import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.www.pagingdemo.api.GithubService
import com.www.pagingdemo.db.RemoteKeys
import com.www.pagingdemo.db.RepoDatabase
import com.www.pagingdemo.model.Repo
import retrofit2.HttpException
import java.io.IOException

/**
 * paging3实现的从网络下载到本地数据源
 * @author MilkCoder
 * @date 2021-09-22
 */

const val IN_QUALIFIER = "in:name,description"

private const val GITHUB_STARTING_PAGE_INDEX = 1

@OptIn(ExperimentalPagingApi::class)
class GithubRemoteMediator(
    private val query: String,
    private val service: GithubService,
    private val repoDatabase: RepoDatabase
) : RemoteMediator<Int, Repo>() {

    //在分页开始时启动远程刷新，不触发远程prepend或
    //添加，直到刷新成功。如果我们不介意展示过时的东西，
    //缓存的脱机数据，我们可以返回SKIP_INITIAL_REFRESH，而不是防止分页
    //触发远程刷新。
    override suspend fun initialize(): InitializeAction {
        return InitializeAction.LAUNCH_INITIAL_REFRESH
    }

    override suspend fun load(loadType: LoadType, state: PagingState<Int, Repo>): MediatorResult {
        val page = when (loadType) {
            LoadType.REFRESH -> {
                val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
                remoteKeys?.nextKey?.minus(1) ?: GITHUB_STARTING_PAGE_INDEX
            }
            LoadType.PREPEND -> {
                val remoteKeys = getRemoteKeyForFirstItem(state)
                // 如果remoteKeys为null，则表示刷新结果尚未在数据库中。
                // 我们可以使用' endOfPaginationReached = false '返回Success，因为如果RemoteKeys变为非空，Paging
                // 将再次调用这个方法。
                // 如果remoteKeys不是NULL，但它的prevKey是NULL，这意味着我们已经到达了prepend的分页结束。
                val prevKey = remoteKeys?.prevKey
                    ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
                prevKey
            }
            LoadType.APPEND -> {
                val remoteKeys = getRemoteKeyForLastItem(state)
                // 如果remoteKeys为null，则表示刷新结果尚未在数据库中。
                // 我们可以使用' endOfPaginationReached = false '返回Success，因为如果RemoteKeys变为非空，Paging
                // 将再次调用这个方法。
                // 如果remoteKeys不是NULL，但它的prevKey是NULL，这意味着我们已经到达了append的分页结束。
                val nextKey = remoteKeys?.nextKey
                    ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
                nextKey
            }
        }

        val apiQuery = query + IN_QUALIFIER

        try {
            val apiResponse = service.searchRepos(apiQuery, page, state.config.pageSize)

            val repos = apiResponse.items
            val endOfPaginationReached = repos.isEmpty()
            repoDatabase.withTransaction {
                // 清除数据库中的所有表
                if (loadType == LoadType.REFRESH) {
                    repoDatabase.remoteKeysDao().clearRemoteKeys()
                    repoDatabase.reposDao().clearRepos()
                }
                val prevKey = if (page == GITHUB_STARTING_PAGE_INDEX) null else page - 1
                val nextKey = if (endOfPaginationReached) null else page + 1
                val keys = repos.map {
                    RemoteKeys(repoId = it.id, prevKey = prevKey, nextKey = nextKey)
                }
                repoDatabase.remoteKeysDao().insertAll(keys)
                repoDatabase.reposDao().insertAll(repos)
            }
            return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
        } catch (exception: IOException) {
            return MediatorResult.Error(exception)
        } catch (exception: HttpException) {
            return MediatorResult.Error(exception)
        }
    }

    private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Repo>): RemoteKeys? {
        //获取最后一个包含条目的页面。
        //从最后一页，获取最后一项
        return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
            ?.let { repo ->
                // 获取检索到的最后一项的远程密钥
                repoDatabase.remoteKeysDao().remoteKeysRepoId(repo.id)
            }
    }

    private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Repo>): RemoteKeys? {
        //获取被检索的、包含条目的第一页。
        //从第一页，得到第一项
        return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
            ?.let { repo ->
                // 获取检索到的第一个项的远程键
                repoDatabase.remoteKeysDao().remoteKeysRepoId(repo.id)
            }
    }

    private suspend fun getRemoteKeyClosestToCurrentPosition(state: PagingState<Int, Repo>): RemoteKeys? {
        //分页库试图在锚点位置之后加载数据
        //获取最接近锚点位置的项目
        return state.anchorPosition?.let { position ->
            state.closestItemToPosition(position)?.id?.let { repoId ->
                repoDatabase.remoteKeysDao().remoteKeysRepoId(repoId)
            }
        }
    }
}